package org.budo.dubbo.protocol.http.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.dubbo.protocol.http.BudoDubboHttpProtocol;
import org.budo.dubbo.protocol.http.api.RegistryApi;
import org.budo.dubbo.protocol.http.api.impl.RegistryApiImpl;
import org.budo.dubbo.protocol.http.exception.BudoRuntimeException;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.nutz.lang.util.MethodParamNamesScaner;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.util.Assert;

import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.RpcResult;

/**
 * @author li
 */
public class BudoReflectionUtil {
    private static final Boolean HAS_NUTZ_METHOD_PARAM_NAMES_SCANER = ReflectUtil.hasClass("org.nutz.lang.util.MethodParamNamesScaner");

    private static final Logger log = LoggerFactory.getLogger(BudoReflectionUtil.class);

    private static final Map<String, String> interfaceNameCache = new ConcurrentHashMap<String, String>();

    private static final Map<String, Method> matchedMethodCache = new ConcurrentHashMap<String, Method>();

    private static final Map<Method, String[]> parameterNamesCache = new ConcurrentHashMap<Method, String[]>();

    private static final Map<String, Object> methodAnnotationCache = new ConcurrentHashMap<String, Object>();

    private static final Map<Method, Method> interfaceMethodCache = new ConcurrentHashMap<Method, Method>();

    /**
     * Spring 4 才有
     */
    private static final boolean HAS_SPRING_PARAMETER_NAME_DISCOVERER = ReflectUtil.hasClass("org.springframework.core.DefaultParameterNameDiscoverer");

    /**
     * TODO 这些可能都不需要每次都来解析，可以初次解析后缓存，或生成代码
     * 
     * @see org.budo.dubbo.protocol.http.AbstractBudoDubboHttpInvoker#processGenericResult(Result,
     *      Invocation)
     */
    public static Class<?> getComponentType(Method method, Integer parameterIndex) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Class<?> parameterType = parameterTypes[parameterIndex];

        if (null == parameterType) {
            return null;
        }

        if (parameterType.isArray()) {
            return parameterType.getComponentType();
        }

        Type[] genericParameterTypes = method.getGenericParameterTypes();
        Type genericParameterType = genericParameterTypes[parameterIndex];

        if (!(genericParameterType instanceof ParameterizedType)) {
            return null;
        }

        ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

        Type actualTypeArgument = actualTypeArguments[0];
        Class parameterComponentType = (Class) actualTypeArgument;
        return parameterComponentType;
    }

    public static <T extends Annotation> T getMethodAnnotationCached(Method method, Class<T> annotationType) {
        if (null == method || null == annotationType) {
            log.error("#88 getMethodAnnotation return null, method=" + method + ", annotationType=" + annotationType);
            return null;
        }

        Class<?> declaringClass = method.getDeclaringClass();
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();

        return getMethodAnnotationCached_2(declaringClass, methodName, parameterTypes, annotationType);
    }

    public static <T extends Annotation> T getMethodAnnotationCached_1(String interfaceType, String methodName, Class<?>[] parameterTypes, Class<? extends Annotation> annotationType) {
        String methodCacheKey = "@" + annotationType.getName() + " " + interfaceType + "." + methodName + "(" + StringUtil.join(parameterTypes) + ")";

        Object value = methodAnnotationCache.get(methodCacheKey);

        if (null != value) {
            if (log.isDebugEnabled()) {
                log.debug("#96 getMethodAnnotation from cache map, methodCacheKey=" + methodCacheKey + ", value=" + value + ", methodAnnotationCache=" + methodAnnotationCache);
            }

            return (value instanceof Annotation) ? (T) value : null; // 空值缓存处理
        }

        // 泛化调用 没有这个接口
        // 泛化调用 的 methodName 不是这个 做不了
        if (!ReflectUtil.hasClass(interfaceType)) {
            Method findMatchedMethod = SomeUtil.findMatchedMethod(interfaceType, methodName);
            T annotation = (T) findMatchedMethod.getAnnotation(annotationType);
            return annotation;
        }

        Class<?> _interfaceType = ReflectUtil.classForName(interfaceType);
        T annotationValue = resolveMethodAnnotation(_interfaceType, methodName, parameterTypes, annotationType);
        if (null == annotationValue) {
            methodAnnotationCache.put(methodCacheKey, "null"); // 空值缓存
        } else {
            methodAnnotationCache.put(methodCacheKey, annotationValue);
        }

        return annotationValue;
    }

    /**
     * 带缓存
     */
    public static <T extends Annotation> T getMethodAnnotationCached_2(Class<?> interfaceType, String methodName, Class<?>[] parameterTypes, Class<? extends Annotation> annotationType) {
        String methodCacheKey = "@" + annotationType.getName() + " " + interfaceType.getName() + "." + methodName + "(" + StringUtil.join(parameterTypes) + ")";

        Object value = methodAnnotationCache.get(methodCacheKey);

        if (null != value) {
            if (log.isDebugEnabled()) {
                log.debug("#96 getMethodAnnotation from cache map, methodCacheKey=" + methodCacheKey + ", value=" + value + ", methodAnnotationCache=" + methodAnnotationCache);
            }

            return (value instanceof Annotation) ? (T) value : null; // 空值缓存处理
        }

        T annotationValue = resolveMethodAnnotation(interfaceType, methodName, parameterTypes, annotationType);
        if (null == annotationValue) {
            methodAnnotationCache.put(methodCacheKey, "null"); // 空值缓存
        } else {
            methodAnnotationCache.put(methodCacheKey, annotationValue);
        }

        return annotationValue;
    }

    private static <T extends Annotation> T resolveMethodAnnotation(Class<?> methodDeclaringType, String methodName, Class<?>[] parameterTypes, Class<? extends Annotation> annotationType) {
        if (null == methodDeclaringType) {
            log.error("#158 resolveMethodAnnotation return null, methodDeclaringType=" + methodDeclaringType + ", methodName=" + methodName + ", annotationType=" + annotationType);
            return null;
        }

        if (FactoryBean.class.isAssignableFrom(methodDeclaringType)) {
            log.warn("#133 resolveMethodAnnotation for FactoryBean return null, " + methodDeclaringType + "." + methodName + "(" + Arrays.toString(parameterTypes) + "), @" + annotationType);
            return null;
        }

        Object beanInstance = null;
        Class<?> beanType = null;
        Method beanMethod = null;

        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstance();

        ApplicationContext applicationContext = null;
        String[] beanNamesForType = null;

        if (null != applicationContextAware) {
            applicationContext = applicationContextAware.getApplicationContext();
            beanNamesForType = applicationContext.getBeanNamesForType(methodDeclaringType);
        }

        if (null != beanNamesForType && beanNamesForType.length > 0) {
            beanInstance = SpringUtil.getBean(applicationContext, methodDeclaringType);
            beanType = beanType(beanInstance);
            beanMethod = findMethod(beanType, methodName, parameterTypes);
        }

        Annotation annotation;
        if (null != beanInstance && null != beanType && null != beanMethod) { // 此处如果不进入也不能return，应当跳到通过接口查找
            annotation = beanMethod.getAnnotation(annotationType);
            if (null != annotation) { // 方法实现上有注解
                if (log.isInfoEnabled()) {
                    log.info("#137 annotation on beanMethod, beanMethod=" + beanMethod + ", beanType=" + beanType + ", beanInstance=" + beanInstance);
                }

                return (T) annotation;
            }

            annotation = beanType.getAnnotation(annotationType);
            if (null != annotation) { // 实现类上有注解
                if (log.isInfoEnabled()) {
                    log.info("#146 annotation on beanType, beanType=" + beanType + ", beanInstance=" + beanInstance);
                }

                return (T) annotation;
            }
        }

        // 通过接口查找注解
        List<Class<?>> interfaceTypes = new ArrayList<Class<?>>();
        interfaceTypes.add(methodDeclaringType);

        List<Class<?>> declaringTypeInterfaces = getInterfaces(methodDeclaringType);
        interfaceTypes.addAll(declaringTypeInterfaces);

        for (Class<?> interfaceType : interfaceTypes) {
            Method interfaceMethod = findMethod(interfaceType, methodName, parameterTypes);
            if (null == interfaceMethod) {
                continue;
            }

            annotation = interfaceMethod.getAnnotation(annotationType);
            if (null != annotation) { // 接口方法上有注解
                if (log.isInfoEnabled()) {
                    log.info("#169 annotation on interfaceMethod, interfaceMethod=" + interfaceMethod //
                            + ", beanType=" + beanType //
                            + ", beanInstance=" + beanInstance //
                            + ", eachInterfaceType=" + interfaceType);
                }

                return (T) annotation;
            }

            annotation = interfaceType.getAnnotation(annotationType);
            if (null != annotation) {// 接口上有注解
                if (log.isInfoEnabled()) {
                    log.info("#181 annotation on interfaceType, beanType=" + beanType + ", beanInstance=" + beanInstance + ", interfaceType=" + interfaceType);
                }

                return (T) annotation;
            }

            if (log.isInfoEnabled()) {
                String beanInstanceToString = null == beanInstance ? null : beanInstance.getClass().getName() + "@" + Integer.toHexString(beanInstance.hashCode());
                log.info("#189 no @SignCheck, beanMethod=" + beanMethod //
                        + ", interfaceMethod=" + interfaceMethod //
                        + ", beanInstance=" + beanInstanceToString //
                        + ", beanType=" + beanType //
                        + ", interfaceType=" + interfaceType);
            }
        }

        return null;
    }

    /**
     * 递归查询类型实现的所有接口
     */
    private static List<Class<?>> getInterfaces(Class<?> type) {
        List<Class<?>> resultInterfaces = new ArrayList<Class<?>>();
        Class<?>[] interfaces = type.getInterfaces();
        for (Class<?> each : interfaces) {
            resultInterfaces.add(each);

            List<Class<?>> interfaces2 = getInterfaces(each);
            resultInterfaces.addAll(interfaces2); // 递归
        }
        return resultInterfaces;
    }

    public static Class<?> beanType(Object beanInstance) {
        if (null == beanInstance) {
            return null;
        }

        Class<? extends Object> beanType = beanInstance.getClass();
        if (beanType.getName().contains("$$EnhancerBySpringCGLIB$$")) {
            return beanType.getSuperclass();
        }

        return beanType;
    }

    /**
     * 判断参数类型是否匹配，类型相等，或是子类型
     * 
     * @param parameterType     形参类型 或方法声明的返回值类型
     * @param argumentValueType 实参类型 或实际返回值类型
     */
    public static boolean isArgumentValueTypeMatchParameterType(Class<?> parameterType, Class<?> argumentValueType) {
        if (null == parameterType || null == argumentValueType) {
            return false;
        }

        if (parameterType.equals(argumentValueType)) {
            return true;
        }

        if ((parameterType.equals(boolean.class) && argumentValueType.equals(Boolean.class)) //
                || (parameterType.equals(Boolean.class) && argumentValueType.equals(boolean.class))) {
            return true;
        }

        if ((parameterType.equals(char.class) && argumentValueType.equals(Character.class)) //
                || (parameterType.equals(Character.class) && argumentValueType.equals(char.class))) {
            return true;
        }

        // 数字类型
        if (ReflectUtil.isNumberType(parameterType) && ReflectUtil.isNumberType(argumentValueType)) {
            return true;
        }

        return parameterType.isAssignableFrom(argumentValueType);
    }

    public static Method getMatchedMethodCached(String interfaceName, String methodName) {
        String cacheKey = interfaceName + "." + methodName;
        Method matchedMethod = matchedMethodCache.get(cacheKey);
        if (null != matchedMethod) {
            return matchedMethod;
        }

        if (!ReflectUtil.hasClass(interfaceName)) {
            log.error("#332 getMatchedMethodCached, not hasClass, interfaceName=" + interfaceName);
            return null;
        }

        matchedMethod = findMatchedMethod(interfaceName, methodName);
        if (null != matchedMethod) {
            matchedMethodCache.put(cacheKey, matchedMethod);
        }

        return matchedMethod;
    }

    public static String[] getParameterNamesCached(Class interfaceType, Method method) {
        if (null == method || null == interfaceType) {
            log.error("#328 getParameterNamesCached error, interfaceType=" + interfaceType + ", method=" + method);
            return null;
        }

        String[] parameterNames = parameterNamesCache.get(method);
        if (null != parameterNames) {
            return parameterNames;
        }

        parameterNames = findParameterNames(interfaceType, method);
        if (null != parameterNames) {
            parameterNamesCache.put(method, parameterNames);
        }

        return parameterNames;
    }

    /**
     * 传入接口不带package的名称，返回匹配的完整类名
     */
    public static String getMatchedInterfaceNameCached(String interfaceNameInUrl) {
        String matchedInterfaceName = interfaceNameCache.get(interfaceNameInUrl); // 缓存一下,不每次都去查找
        if (!StringUtils.isEmpty(matchedInterfaceName)) {
            return matchedInterfaceName;
        }

        matchedInterfaceName = findMatchedInterfaceName(interfaceNameInUrl);
        if (null != matchedInterfaceName) {
            interfaceNameCache.put(interfaceNameInUrl, matchedInterfaceName);
        }

        return matchedInterfaceName;
    }

    public static Class getResultType(String resultTypeName) {
        if (null == resultTypeName) {
            return null;
        }

        if (RpcResult.class.getName().equals(resultTypeName)) {
            return RpcResult.class;
        }

        return ReflectUtil.classForName(resultTypeName);
    }

    /**
     * 仅根据方法名匹配方法，存在方法重载情况下，可能不准确
     */
    private static Method findMatchedMethod(String interfaceName, String methodName) {
        Class<?> interfaceType = ReflectUtil.classForName(interfaceName);
        Method[] methods = interfaceType.getMethods();

        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("#407 findMatchedMethod, return null, interfaceName=" + interfaceName + ", methodName=" + methodName);
        }

        return null;
    }

    private static String[] findParameterNames(Class interfaceType, Method method) {
        ApplicationContext applicationContext = BudoBeanUtil.getApplicationContext();
        Assert.notNull(applicationContext, "#366 no ApplicationContext found, try BudoApplicationContextAware");

        Class<?> instanceType = null;
        if (RegistryApi.class.equals(interfaceType)) {
            instanceType = RegistryApiImpl.class;
        } else {
            Object beanInstance = applicationContext.getBean(interfaceType);
            BeanDefinition beanDefinition = SpringUtil.getBeanDefinition(applicationContext, interfaceType);
            if (beanDefinition.getBeanClassName().equals("com.alibaba.dubbo.config.spring.ReferenceBean")) {
                log.error("#413 bean of interfaceType is a dubbo Reference, we can not got parameter names for method " + method);
            }

            if (beanInstance.getClass().getName().startsWith("com.sun.proxy")) {
                instanceType = ReflectUtil.classForName(beanDefinition.getBeanClassName());
            } else {
                instanceType = beanInstance.getClass();
            }
        }

        // 取实现类上的方法名
        Method implMethod = getMethod(instanceType, method.getName(), method.getParameterTypes());
        return getMethodParameterNames(implMethod);
    }

    private static String findMatchedInterfaceName(String interfaceNameInUrl) {
        BudoDubboHttpProtocol budoDubboHttpProtocol = BudoDubboHttpProtocol.getInstance();
        Map<String, Exporter<?>> exporterMap = budoDubboHttpProtocol.getExporterMap();

        if (exporterMap.containsKey(interfaceNameInUrl)) { // 传入的就是完整的类名，直接返回
            return interfaceNameInUrl;
        }

        for (Entry<String, Exporter<?>> entry : exporterMap.entrySet()) {
            String key = entry.getKey();
            if (key.endsWith("." + interfaceNameInUrl)) {
                return key; // 根据Simple类名匹配
            }
        }

        return null;
    }

    public static List<Field> getAllFields(Class<?> type) {
        List<Field> fields = new ArrayList<Field>();
        Field[] declaredFields = type.getDeclaredFields();
        Collections.addAll(fields, declaredFields);

        Class<?> superclass = type.getSuperclass();
        if (null != superclass && Object.class != superclass) {// 扫描超类的Field
            List<Field> superFields = getAllFields(superclass);
            fields.addAll(superFields);
        }

        return fields;
    }

    /**
     * 返回方法形参名列表，有可能会取不到
     */
    public static String[] getMethodParameterNames(Method method) {
        if (HAS_SPRING_PARAMETER_NAME_DISCOVERER) { // spring 4 才有这个 之前的没有
            return getParameterNames(method);
        }

        if (HAS_NUTZ_METHOD_PARAM_NAMES_SCANER) {
            List<String> paramNames = MethodParamNamesScaner.getParamNames(method);
            return null == paramNames ? null : paramNames.toArray(new String[0]);
        }

        log.error("#471 getMethodParameterNames, method=" + method);
        return null;
    }

    private static String[] getParameterNames(Method method) {
        DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

        if (null != parameterNames) {
            return parameterNames;
        }

        Class<?> declaringClass = method.getDeclaringClass();
        Class<?> superType = declaringClass.getSuperclass();

        if (null == superType) {
            superType = declaringClass; // 类上的方法，不是接口定义的方法
        }

        if (superType.equals(Proxy.class)) {
            superType = methodDeclaringClass(method);
        }

        if (!superType.equals(Object.class) && !superType.equals(declaringClass)) {
            Method superMethod = ReflectUtil.getMethod(superType, method.getName(), method.getParameterTypes());
            return getMethodParameterNames(superMethod);
        }

        log.error("#502 getParameterNames, method=" + method);
        return null;
    }

    private static Class<?> methodDeclaringClass(Method method) {
        Class<?>[] interfaces = method.getDeclaringClass().getInterfaces();
        for (Class<?> interfaceType : interfaces) {
            if (interfaceType.getName().startsWith("org.springframework.")) {
                continue;
            }

            ApplicationContext applicationContext = BudoApplicationContextAware.getInstanceNoNull().getApplicationContext();
            BeanDefinition beanDefinition = SpringUtil.getBeanDefinition(applicationContext, interfaceType);
            String beanClassName = beanDefinition.getBeanClassName();
            Class beanType = ReflectUtil.classForName(beanClassName);
            return beanType;
        }

        throw new RuntimeException("#469 method=" + method + ", methodDeclaringClass not found");
    }

    /**
     * 获取方法，如果没有，就返回空
     * 
     * @param targetType
     * @param methodName
     * @param parameterTypes
     */
    private static Method findMethod(Class<?> targetType, String methodName, Class<?>[] parameterTypes) {
        if (null == targetType || null == methodName || null == parameterTypes) {
            log.error("#440 findMethod return null, targetType=" + targetType + ", methodName=" + methodName + ", parameterTypes=" + StringUtil.join(parameterTypes));
            return null;
        }

        try {
            return targetType.getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            return null;
        } catch (Throwable e) {
            throw new BudoRuntimeException(e);
        }
    }

    public static Method getMethod(Class<?> targetType, String methodName, Class<?>[] parameterTypes) {
        if (null == targetType || null == methodName || null == parameterTypes) {
            log.error("#450 getMethod return null, targetType=" + targetType + ", methodName=" + methodName + ", parameterTypes=" + StringUtil.join(parameterTypes));
            return null;
        }

        try {
            return targetType.getMethod(methodName, parameterTypes);
        } catch (Throwable e) {
            throw new BudoRuntimeException(e);
        }
    }

    public static Method getInterfaceMethodCached(Method method) {
        Method interfaceMethod = interfaceMethodCache.get(method);

        if (null == interfaceMethod) {
            interfaceMethod = getInterfaceMethod(method);
            interfaceMethodCache.put(method, interfaceMethod);
        }

        return interfaceMethod;
    }

    public static Method getInterfaceMethod(Method method) {
        Class<?> declaringClass = method.getDeclaringClass();

        if (declaringClass.isInterface()) {
            return null;
        }

        Class<?>[] interfaces = declaringClass.getInterfaces();
        for (Class<?> interfaceType : interfaces) {
            Method interfaceMethod = ReflectUtil.findMethod(interfaceType, method.getName(), method.getParameterTypes());
            if (null != interfaceMethod) {
                return interfaceMethod;
            }
        }

        return null;
    }

    public static String getParameterNameCached(Class<?> interfaceType, Method method, Integer parameterIndex) {
        String[] parameterNames = getParameterNamesCached(interfaceType, method);
        if (null == parameterNames || null == parameterIndex) {
            return null; // parameterNames 有可能为空，如果实现类是普通类就应该取得到，如果是 DubboReference 就取不到
        }

        return parameterNames[parameterIndex];
    }
}
